import {getAbsoluteElementPosition, isArray} from "../functions/common.js";

// get div obj
const anime_div = document.getElementById("anime_div");

// get a block cell
const createResourceBlock = () => {
    let div = document.createElement("div");
    div.style.width = "80px";
    div.style.height = "30px";
    div.style.display = "inline-block";
    div.style.position = "absolute";
    div.style.top = "-50px";
    return div;
};

// get a location block
const createLocationBlock = () => {
    let elem = createResourceBlock();
    elem.style.border = "1px dotted gray";
    return elem;
};

// get a real resource block
const createRealResourceCell = () => {
    let elem = createResourceBlock();
    elem.style.backgroundColor = "DodgerBlue";
    return elem;
};

// Some configurations.
const blank_block_config = {
    begin_y: 40,
    begin_x: 50,
    block_height: 50,
    column_width: 120,
};

// List of 5 process and system
let listOfFiveProcessAndSystem = [
    {column_name: "System", blank_blocks: [], resource_blocks: [], begin_x: blank_block_config.begin_x},
];

// init List of 5 process and system, and assume that there are only one resources.
const initListOfFiveProcessAndSystem = ({system, processes}) => {
    // Generate system's title.
    const getColumnTitle = (text, begin_x) => {
        let elem = document.createElement("div");
        elem.innerText = text;
        elem.style.top = blank_block_config.begin_y - 30;
        elem.style.position = "absolute";
        elem.style.width = "100px";
        elem.style.fontSize = "20px";
        elem.style.left = String(begin_x) + "px";
        return elem;
    };
    anime_div.appendChild(getColumnTitle("System", listOfFiveProcessAndSystem[0].begin_x));
    // Generate system's blank blocks.
    for (let i = 0; i < system; i++) {
        let elem = {
            elem_index: i,
            position_y: blank_block_config.begin_y + i * blank_block_config.block_height,
            blank_node: createLocationBlock(),
        };
        // Set the blank block's position.
        elem.blank_node.style.top = String(elem.position_y) + "px";
        elem.blank_node.style.left = String(listOfFiveProcessAndSystem[0].begin_x) + "px";
        // Attach this block to parent div.
        anime_div.appendChild(elem.blank_node);
        listOfFiveProcessAndSystem[0].blank_blocks.push(elem);
        // Generate resource blocks.
        let elem_div = createRealResourceCell();
        elem_div.style.top = String(elem.position_y) + "px";
        elem_div.style.left = String(listOfFiveProcessAndSystem[0].begin_x) + "px";
        anime_div.appendChild(elem_div);
        listOfFiveProcessAndSystem[0].resource_blocks.push(elem_div);
    }
    // Generate processes's blank blocks.
    for (let i = 0; i < 5; i++) {
        let tmp = {column_name: "Process", blank_blocks: [], resource_blocks: [], begin_x: 0};
        tmp.column_name = tmp.column_name + String(i);
        tmp.begin_x = listOfFiveProcessAndSystem[i].begin_x + blank_block_config.column_width;
        // Generate processes' title.
        anime_div.appendChild(getColumnTitle(tmp.column_name, tmp.begin_x));
        // Generate child block through processes
        for (let j = 0; j < processes[i].need; j++) {
            let tmp_div = createLocationBlock();
            tmp_div.style.top = String(blank_block_config.begin_y + j * blank_block_config.block_height) + "px";
            tmp_div.style.left = String(tmp.begin_x) + "px";
            // Attach this block to parent div.
            anime_div.appendChild(tmp_div);
            tmp.blank_blocks.push(tmp_div);
        }
        listOfFiveProcessAndSystem.push(tmp);
    }
    // allocate resource blocks
    setTimeout(() => {
        moveResourceBlocksTo(processes);
    }, 300);
};

// Move blocks to given location when the pages has been loaded.
const moveResourceBlocksTo = (processes) => {
    if (processes instanceof Array) {
        for (let i = 0; i < processes.length; i++) {
            // Variable allocated presents the num the process i needs.
            let allocated = processes[i].allocated;
            for (let j = 0; j < allocated; j++) {
                let allocated_div = listOfFiveProcessAndSystem[0].resource_blocks.pop();
                listOfFiveProcessAndSystem[i + 1].resource_blocks.push(allocated_div);
                let targetLocation = {
                    x: blank_block_config.begin_x + (i + 1) * blank_block_config.column_width,
                    y: blank_block_config.begin_y + (listOfFiveProcessAndSystem[i + 1].resource_blocks.length - 1) * blank_block_config.block_height,
                };
                let thisLocation = getAbsoluteElementPosition(allocated_div);
                // set the action.
                let action = {
                    targets: allocated_div,
                    translateX: targetLocation.x - thisLocation.x,
                    translateY: targetLocation.y - thisLocation.y,
                    easing: "spring(1, 80, 15, 0)",
                };
                // perform the action.
                anime(action);
            }
        }
    }
};

// Input args
let input = {
    system: 10,
    processes: [
        {key: 0, need: 3, allocated: 1},
        {key: 0, need: 2, allocated: 1},
        {key: 0, need: 2, allocated: 2},
        {key: 0, need: 2, allocated: 1},
        {key: 0, need: 3, allocated: 1},
    ]
};

// Generate a action
const generateAction = (from_A, to_B, num) => {
    return { from_A, to_B, num };
};

// Abstract Function: move {num} blocks from stack A to stack B! Complete!
const moveFromAToB = (from_A, to_B, num) => {
    if (listOfFiveProcessAndSystem[from_A].resource_blocks.length >= num) {
        let stack_B = listOfFiveProcessAndSystem[to_B].resource_blocks;
        let stack_A = listOfFiveProcessAndSystem[from_A].resource_blocks;
        for (let i = 0; i < num; i++) {
            let block = stack_A.pop();
            let thisPosition = getAbsoluteElementPosition(block);
            let targetPosition = {
                x: listOfFiveProcessAndSystem[to_B].begin_x,
                y: blank_block_config.begin_y + stack_B.length * blank_block_config.block_height,
            };
            stack_B.push(block);
            anime({
                targets: block,
                translateX: targetPosition.x - thisPosition.x,
                translateY: targetPosition.y - thisPosition.y,
                easing: "spring(1, 80, 15, 0)",
            })
        }
    }
};

// Banker: Judge whether this situation is safe?
const judgeSafe = (system, processes) => {
    if (isArray(processes)) {
        let finish = [];
        processes.map((process)=>{
            if (process.need===process.allocated){
                finish.push(true);
                system += process.need;
            } else {
                finish.push(false);
            }
        });
        return canFindOnePath(system, processes, finish);
    }
    return false;
};

// Banker: find Path
const canFindOnePath = (system, process, finish) => {
    if (isArray(process) && isArray(finish)){
        // check whether all is finished
        let all_finish = true;
        for (let i = 0;i<process.length;i++){
            if (!finish[i]) all_finish = false;
        }
        if (all_finish) return true;
        for (let i = 0;i<process.length;i++){
            let findOne = false;
            // Before this, all finished should be registered.
            if (!finish[i]){
                let allocated_num = process[i].need - process[i].allocated;
                // The process needs to be finished
                if (allocated_num <= system){
                    // Allocate and recycle
                    system += process[i].allocated;
                    finish[i] = true;
                    findOne = canFindOnePath(system, process, finish);
                    system -= process[i].allocated;
                    finish[i] = false;
                }
            }
            if (findOne) return true;
        }
    }
    return false;
};

// Banker: Judge and Build one safe?
const judgeAndBuildSafe = (system, processes) => {
    if (isArray(processes)) {
        let finish = [];
        for (let i = 0;i<processes.length;i++){
            if (processes[i].need===processes[i].allocated){
                finish.push(true);
                system += processes[i].need;
                animationQueue.push(generateAction(i+1, 0, processes[i].need));
            } else {
                finish.push(false);
            }
        }
        return buildOnePath(system, processes, finish);
    }
    return false;
};

// Banker: build path
const buildOnePath = (system, process, finish) => {
    if (isArray(process) && isArray(finish)){
        // check whether all is finished
        let all_finish = true;
        for (let i = 0;i<process.length;i++){
            if (!finish[i]) all_finish = false;
        }
        if (all_finish) return true;
        // Find one path to allocate.
        for (let i = 0;i<process.length;i++){
            let findOne = false;
            if (!finish[i]){
                let allocated_num = process[i].need - process[i].allocated;
                // The process needs to be finished
                if (allocated_num <= system){
                    // Allocate and recycle
                    system += process[i].allocated;
                    finish[i] = true;
                    animationQueue.push(generateAction(0, i+1, allocated_num));
                    animationQueue.push(generateAction(i+1, 0, process[i].need));
                    findOne = buildOnePath(system, process, finish);
                    if (!findOne){
                        animationQueue.pop();
                        animationQueue.pop();
                    }
                    system -= process[i].allocated;
                    finish[i] = false;
                }
            }
            if (findOne) return true;
        }
    }
    return false;
};

// Animation Queue
let animationQueue = [];

// animationTimer
let animationInterval = null;

// start the power point
const startPowerPoint = () => {
    if (animationInterval === null) {
        console.log("startPowerPoint");
        animationInterval = setInterval(() => {
            if (animationQueue.length>0){
                let {from_A, to_B, num} = animationQueue.shift();
                console.log(from_A, to_B, num);
                moveFromAToB(from_A, to_B, num);
            }else {
                // console.log("No actions!");
                stopPowerPoint();
            }
        }, 1000)
    }
};

// stop the power point
const stopPowerPoint = () => {
    console.log("stopPowerPoint");
    window.clearInterval(animationInterval);
    animationInterval = null;
};

// Generate the input form above.
const generateTopForm = () => {
    // Define a function to generate div.
    const generateNormalDiv = () => {
        return document.createElement("div");
    };
    const generateLayuiInput = (initValue, placeholder, onChange, width) => {
        let input_a = document.createElement("input");
        input_a.type = "text";
        input_a.className = "layui-input";
        input_a.placeholder = placeholder;
        input_a.onchange = onChange;
        input_a.value = initValue;
        input_a.style.width = width;
        input_a.style.marginRight = "20px";
        input_a.style.display = "inline-block";
        return input_a;
    };
    // Get the box div
    let box_div = document.getElementById("table_input");
    // Generate the system div.
    let system_div = generateNormalDiv();
    box_div.appendChild(system_div);
    let system_input = generateLayuiInput(input.system, "Input the num of resources",(event)=>{
        let new_value = event.target.value;
        let change_value = 0;
        try {
            if (new_value !== null && new_value !== ""){
                change_value = parseInt(new_value);
                if (change_value>10){
                    change_value = 10;
                    changeSystemInputTo(10);
                    alert("Numbers greater than 10 are not currently supported")
                }else if (change_value < 1) {
                    change_value = 1;
                    changeSystemInputTo(1);
                    alert("System resources must be greater than 0")
                }
                input.system = change_value;
            }
        }catch (e) {
            alert("Error: Illegal Input!")
        }
    }, "160px");
    const changeSystemInputTo =(new_value) => {
        system_input.value = new_value;
    };
    system_div.appendChild(system_input);
    // All progress input.
    let allProgressInput = [];
    const setProgressInputValue = (index, need, allocated) => {
        allProgressInput[index].value = String(allocated) +"/" + String(need);
    };
    for (let i = 0;i<5;i++){
        let init_value = String(input.processes[i].allocated) + "/" + String(input.processes[i].need);
        let progress_input = generateLayuiInput(init_value, "Progress "+String(i),(event)=>{
            let numStrArray = event.target.value.split("/");
            console.log(numStrArray);
            if (numStrArray.length!==2){
                alert("Error: Illegal Input");
                return false;
            }
            let first = 0, second = 0;
            try {
                first = parseInt(numStrArray[0]);
                second = parseInt(numStrArray[1]);
                if (second > input.system){
                    second = input.system;
                    first = Math.min(first, input.system);
                    setProgressInputValue(i, second, first);
                    input.processes[i].need = second;
                    input.processes[i].allocated = first;
                    alert("Need should be smaller than system");
                    return false;
                }
                if (first > second) {
                    first = second;
                    setProgressInputValue(i, second, first);
                    input.processes[i].need = second;
                    input.processes[i].allocated = first;
                    alert("Allocated should be smaller than needed");
                    return false;
                }
                input.processes[i].need = second;
                input.processes[i].allocated = first;
                console.log(input);
            }catch (e) {
                setProgressInputValue(i, input.processes[i].need, input.progress[i].allocated);
                alert("Error: Illegal Input");
                return false;
            }
        }, "80px");
        allProgressInput.push(progress_input);
        system_div.appendChild(progress_input);
    }
};

// To bind the animation event of button.
layui.use(["jquery","table"], () => {
    const $ = layui.jquery, table = layui.table;
    let buttonMove = $("#move01");
    buttonMove.on("click", () => {
        console.log(input);
        if (!buttonMove.hasClass("layui-btn-disabled")) {
            initListOfFiveProcessAndSystem(input);
            buttonMove.addClass("layui-btn-disabled");
        }
    });
    let buttonGo = $("#move02");
    buttonGo.on("click", () => {
        if (!buttonGo.hasClass("layui-btn-disabled")) {
            if (judgeSafe(input.system, input.processes)){
                judgeAndBuildSafe(input.system, input.processes);
                console.log(animationQueue);
                startPowerPoint();
            } else {
                alert("System not safe.")
            }
            buttonGo.addClass("layui-btn-disabled");
        }
    });
    generateTopForm();
});
